﻿//-------------------------------------------------------------------------------
// <copyright file="InMemoryWorkspace.cs" company="bbv Software Services AG">
//   Copyright (c) 2009 bbv Software Services AG
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
// </copyright>
//------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq.Expressions;
using bbv.DomainDrivenDesign.QueryableContexts;

namespace bbv.DomainDrivenDesign.Workspaces
{
    using System;
    using System.Data;
    using Contexts;
    using Interfaces;

    /// <summary>
    /// Implements a unit of work as a in memory data source.
    /// This class is useful for unit tests.
    /// </summary>
    public class InMemoryWorkspace : IWorkspace
    {
        private Dictionary<object, object> m_loadWithOptions;

        /// <summary>
        /// Holds a reference to the <see cref="InMemoryContext"/>.
        /// </summary>
        private readonly InMemoryContext context;

        /// <summary>
        /// Initializes a new instance of the <see cref="InMemoryWorkspace"/> class.
        /// </summary>
        public InMemoryWorkspace()
        {
            this.context = new InMemoryContext();
            m_loadWithOptions = new Dictionary<object, object>();
        }

        /// <summary>
        /// Gets the underlying connection of the unit of work.
        /// </summary>
        /// <value>The connection as a <see cref="IDbConnection"/>.</value>
        public IDbConnection Connection
        {
            get { throw new NotSupportedException("Connection property is not supported by the InMemoryUnitOfWork"); }
        }

        /// <summary>
        /// Creates an <see cref="T:bbv.DomainDrivenDesign.Interfaces.IQueryableContext`1"/> in the current object context
        /// </summary>
        /// <typeparam name="T">The type of the queryable context.</typeparam>
        /// <returns>An <see cref="T:bbv.DomainDrivenDesign.Interfaces.IQueryableContext`1"/> of the specified type.</returns>
        public IQueryableContext<T> CreateQuery<T>() where T : class
        {
            return this.context.CreateQuery<T>();
        }
        /// <summary>
        /// Persists all pending updates to the data source.
        /// </summary>
        public void SubmitChanges()
        {
            // all changes made to the data source are automatically submitted.
        }

        /// <summary>
        /// Accepts all changes made to objects 
        /// in the <see cref="IWorkspace"/> context.
        /// </summary>
        public void AcceptAllChanges()
        {
            // all changes made to the data source are automatically submitted and accepted.
        }

        public T GetById<T, K>(K id) where T : class
        {
            return context.GetById<T,K>(id);
        }



        /// <summary>
        /// Return the persistent instance of the given named entity with the given identifier,
        /// or null if there is no such persistent instance. (If the instance, or a proxy for the
        /// instance, is already associated with the session, return that instance or proxy.)
        /// </summary>
        /// <typeparam name="T">the type of the entity</typeparam>
        /// <param name="entityWithKey">an entity with a key</param>
        /// <returns>a persistent instance or null</returns>
        public T Get<T>(T entityWithKey) where T : class
        {
            throw new NotSupportedException("The method Get is not supported by the InMemoryUnitOfWork");
        }

        /// <summary>
        /// Inserts an entity in a pending insert state to the <see cref="IWorkspace"/> context.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entity">A transient instance of a persistent class.</param>
        public void Add<T>(T entity) where T : class
        {
            this.context.GetTable<T>().Insert(entity);
        }

        public T Create<T>(T entity) where T : class
        {
            this.context.GetTable<T>().Insert(entity);
            return entity;
        }

        /// <summary>
        /// Remove an entity from this table into a pending delete state.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entity">The instance to be removed.</param>
        public void Delete<T>(T entity) where T : class
        {
            this.context.GetTable<T>().Delete(entity);
        }

        /// <summary>
        /// Update the persistent instance with the identifier of the given transient instance.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entity">The detached object that has property updates
        /// to apply to the original object.</param>
        /// <remarks>
        /// If there is a persistent instance with the same identifier, an exception is thrown. If
        /// the given transient instance has a <see langword="null"/> identifier, an exception will be thrown.
        /// </remarks>
        public void Update<T>(T entity) where T : class
        {
            this.context.GetTable<T>().Update(entity);
        }

        /// <summary>
        /// Cleans all caches of the <see cref="IWorkspace"/>
        /// and removes all objects from the identity map.
        /// </summary>
        public void Clean()
        {
            // do nothing because we have no cached data
        }

        /// <summary>
        /// Updates an object in the object context with data from the persisted store.
        /// Overrides all the current values with the values from the database.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entity">The entity to be refreshed.</param>
        public void Refresh<T>(T entity) where T : class
        {
            throw new NotSupportedException("The method Refresh is not supported by the InMemoryUnitOfWork");
        }

        /// <summary>
        /// Attach an object or object graph to the object context if the object
        /// has an entity key.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entityWithKey">The entity.</param>
        public void Attach<T>(T entityWithKey) where T : class
        {
            this.context.GetTable<T>().Attach(entityWithKey);
        }

        /// <summary>
        /// Removes the specified object from the <see cref="IWorkspace"/> context.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="entity">The entity.</param>
        public void Detach<T>(T entity) where T : class
        {
            // not used by the InMemoryUnitOfWork
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, 
        /// releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            // no unmanaged resources are used
        }

        public void SetIdFunc<T, TProperty>(Func<T, TProperty,bool> func) where T:class
        {
            context.SetIdFunc(func);
        }
        public void LoadJoinOption<T, TJoinWith, TKey>(Func<T, TKey> outerKeySelector, Func<TJoinWith, TKey> innerKeySelector, Func<T, TJoinWith, T> projection)
            where T : class
            where TJoinWith : class
        {
            context.LoadJoinOption(outerKeySelector, innerKeySelector, projection);
        }
        public void LoadGroupJoinOption<T, TJoinWith, TKey>(Func<T, TKey> outerKeySelector, Func<TJoinWith, TKey> innerKeySelector, Func<T, IEnumerable<TJoinWith>, T> projection)
            where T : class
            where TJoinWith : class
        {
            context.LoadGroupJoinOption(outerKeySelector, innerKeySelector, projection);
        }
    }
}